/** @file
  The EFI Legacy BIOS Patform Protocol is used to mate a Legacy16
  implementation with this EFI code. The EFI driver that produces
  the Legacy BIOS protocol is generic and consumes this protocol.
  A driver that matches the Legacy16 produces this protocol

 @copyright
  INTEL CONFIDENTIAL
  Copyright 1999 - 2018 Intel Corporation.

  The source code contained or described herein and all documents related to the
  source code ("Material") are owned by Intel Corporation or its suppliers or
  licensors. Title to the Material remains with Intel Corporation or its suppliers
  and licensors. The Material may contain trade secrets and proprietary and
  confidential information of Intel Corporation and its suppliers and licensors,
  and is protected by worldwide copyright and trade secret laws and treaty
  provisions. No part of the Material may be used, copied, reproduced, modified,
  published, uploaded, posted, transmitted, distributed, or disclosed in any way
  without Intel's prior express written permission.

  No license under any patent, copyright, trade secret or other intellectual
  property right is granted to or conferred upon you by disclosure or delivery
  of the Materials, either expressly, by implication, inducement, estoppel or
  otherwise. Any license under such intellectual property rights must be
  express and approved by Intel in writing.

  Unless otherwise agreed by Intel in writing, you may not remove or alter
  this notice or any other notice embedded in Materials by Intel or
  Intel's suppliers or licensors in any way.

  This file contains a 'Sample Driver' and is licensed as such under the terms
  of your license agreement with Intel or your vendor. This file may be modified
  by the user, subject to the additional terms of the license agreement.

@par Specification Reference:
**/


#include "LegacyBiosPlatform.h"
#include <Library/EcMiscLib.h>
#include <Protocol/AmtWrapperProtocol.h>
#include <Library/LegacyBiosPlatformLib.h>
#include <Library/BaseMemoryLib.h>
#include "GetImage.h"
#include <Protocol/UsbIo.h>
#include <Library/UefiLib.h>
#include <Library/BiosIdLib.h>
#include <PlatformInfo.h>
#include <Library/TbtCommonLib.h>

#ifdef AMT_SUPPORT
GLOBAL_REMOVE_IF_UNREFERENCED CHAR16    gMeBiosExtensionSetupName[] = ME_BIOS_EXTENSION_SETUP_VARIABLE_NAME;
#endif

//
// The signature of light version of AHCI ROM
//
#define LIGHT_AHCI_ROM_SIGNATURE       0x41325324

GLOBAL_REMOVE_IF_UNREFERENCED BOOLEAN             mEnableCachedPlatformHandle = FALSE;
#define WDT_TIMEOUT   8 // in seconds
GLOBAL_REMOVE_IF_UNREFERENCED EFI_LEGACY_IRQ_PRIORITY_TABLE_ENTRY IrqPriorityTable[MAX_IRQ_PRIORITY_ENTRIES] = {
  { 11, PCI_UNUSED },
  { 10, PCI_UNUSED },
  { 0,  PCI_UNUSED },
  { 0,  PCI_UNUSED },
  { 0,  PCI_UNUSED },
  { 0,  PCI_UNUSED },
  { 0,  PCI_UNUSED }
  };

GLOBAL_REMOVE_IF_UNREFERENCED SYSTEM_ROM_TABLE    SystemOemIntTable [] = {
  {OEM_INT_15_BOOT_DISPLAY_GUID,  TRUE},
  {OEM_INT_15_PANEL_TYPE_GUID,    TRUE},
  {OEM_INT_15_PANEL_FITTING_GUID, TRUE},
  {OEM_INT_15_BOOT_TVMODE_GUID,   TRUE},
  {OEM_INT_15_GET_MISC_GUID,      TRUE},
  {NULL_ROM_FILE_GUID,            FALSE}
  };

GLOBAL_REMOVE_IF_UNREFERENCED SERVICE_ROM_TABLE   ServiceRomTable[] = {
  {PXE_BASE_OPTION_ROM_FILE_GUID, TRUE},
  {NULL_ROM_FILE_GUID,            FALSE}
  };

GLOBAL_REMOVE_IF_UNREFERENCED SYSTEM_ROM_TABLE    SystemRomTable[] = {
  {SYSTEM_ROM_FILE_GUID,         TRUE},
  {NULL_ROM_FILE_GUID,           FALSE}
  };

//
// Module Global:
//  Since this driver will only ever produce one instance of the Private Data
//  protocol you are not required to dynamically allocate the PrivateData.
//
GLOBAL_REMOVE_IF_UNREFERENCED LEGACY_BIOS_PLATFORM_INSTANCE       mPrivateData;
GLOBAL_REMOVE_IF_UNREFERENCED EFI_HANDLE                          mVgaHandles[0x20];
GLOBAL_REMOVE_IF_UNREFERENCED EFI_HANDLE                          mIsaHandles[0x20];
GLOBAL_REMOVE_IF_UNREFERENCED BOOLEAN                             mVgaHandleValid = FALSE;
GLOBAL_REMOVE_IF_UNREFERENCED BOOLEAN                             mIsaHandleValid = FALSE;
GLOBAL_REMOVE_IF_UNREFERENCED UINTN                               mIsaHandleCount = 0;
GLOBAL_REMOVE_IF_UNREFERENCED BOOLEAN                             mLegacyAhciRom  = FALSE;
GLOBAL_REMOVE_IF_UNREFERENCED BOOLEAN                             mSolEnabled     = FALSE;
GLOBAL_REMOVE_IF_UNREFERENCED BOOLEAN                             mWdtEnabled;
GLOBAL_REMOVE_IF_UNREFERENCED BOOLEAN                             mUsbKeyboardPresent = FALSE;

static BOOLEAN                      mBridgeCheckDone;
static UINTN                        gSecondaryBus;
static UINTN                        gSubordinateBus;


/**
  Check for Thunderbolt(TM) bridge by going through all the bridges
  that are available on platform and get the bus range in which
  endpoint devices are connected.

  @param[out] SecondaryBus
  @param[out] SubordinateBus

  @retval     EFI_NOT_FOUND
  @retval     EFI_SUCCESS
**/
EFI_STATUS
EFIAPI
CheckForThunderboltBridge (
  OUT UINTN  *SecondaryBus,
  OUT UINTN  *SubordinateBus
  )
{
  EFI_STATUS                    Status;
  UINTN                         HandleCount;
  EFI_HANDLE                    *HandleBuffer;
  EFI_PCI_IO_PROTOCOL           *PciIo;
  UINTN                         Index;
  PCI_TYPE01                    PciConfigHeader;
  UINT16                        VendorId;
  UINT16                        DeviceId;
  UINTN                         PciSegment;
  UINTN                         PciBus;
  UINTN                         PciDevice;
  UINTN                         PciFunction;

  *SecondaryBus = 0;
  *SubordinateBus = 0;

  Status = gBS->LocateHandleBuffer (
                  ByProtocol,
                  &gEfiPciIoProtocolGuid,
                  NULL,
                  &HandleCount,
                  &HandleBuffer
                  );
  if (EFI_ERROR (Status)) {
    return EFI_NOT_FOUND;
  }

  mBridgeCheckDone = TRUE ;

  for (Index = 0; Index < HandleCount; Index++) {
    Status = gBS->HandleProtocol (
                    HandleBuffer[Index],
                    &gEfiPciIoProtocolGuid,
                    (VOID **) &PciIo
                    );
    if (EFI_ERROR (Status)) {
      continue;
    }
    PciIo->Pci.Read (
                 PciIo,
                 EfiPciIoWidthUint32,
                 0,
                 sizeof (PciConfigHeader) / sizeof (UINT32),
                 &PciConfigHeader
                 );

    if (IS_PCI_P2P (&PciConfigHeader)) {
      PciIo->GetLocation (
               PciIo,
               &PciSegment,
               &PciBus,
               &PciDevice,
               &PciFunction
               );
      PciIo->Pci.Read (
               PciIo,
               EfiPciIoWidthUint16,
               PCI_VENDOR_ID_OFFSET,
               1,
               &VendorId
               );
      PciIo->Pci.Read (
               PciIo,
               EfiPciIoWidthUint16,
               PCI_DEVICE_ID_OFFSET,
               1,
               &DeviceId
               );

      if (IsTbtHostRouter(DeviceId)) {
        //
        // Getting exact range of Secondary & Subordinate Bus
        //
        if((*SecondaryBus == 0 && *SubordinateBus == 0)) {
          *SecondaryBus   = PciConfigHeader.Bridge.SecondaryBus;
          *SubordinateBus = PciConfigHeader.Bridge.SubordinateBus;
        }
        if(*SecondaryBus > PciConfigHeader.Bridge.SecondaryBus) {
          *SecondaryBus   = PciConfigHeader.Bridge.SecondaryBus;
        }
        if(*SubordinateBus < PciConfigHeader.Bridge.SubordinateBus) {
          *SubordinateBus = PciConfigHeader.Bridge.SubordinateBus;
        }
      }
    }//if P2P
  }
  return EFI_SUCCESS;
}


///
/// Function implementations
///

/**
  Find all OEM custom INT snippets

  @param[out] DataImage     All snippet image for the platform concatenated.
  @param[out] DataSize      Size of sum of all images in bytes
  @param[out] Count         Number of snippets found.

  @retval     EFI_SUCCESS   One or more snippets found
  @retval     EFI_NOT_FOUND No snippets found
**/
EFI_STATUS
GetOemIntDataSnippets (
  OUT VOID                               **DataImage,
  OUT UINT32                             *DataSize,
  OUT UINT16                             *Count
  )
{
  EFI_STATUS                    Status;
  UINTN                         TableIndex;
  UINTN                         Index;
  VOID                          *LocalDataImage;
  UINTN                         LocalDataSize;
  UINT32                        TotalSize;
  VOID                          *FoundLocalDataImage[20];
  UINT32                        FoundLocalDataSize[20];
  UINT16                        LocalCount;
  VOID                          *FullTable;
  VOID                          *CurrentPtr;

  TotalSize   = 0;
  LocalCount  = 0;

  //
  // Loop through table of snippet binary descriptions
  //
  for (TableIndex = 0; SystemOemIntTable[TableIndex].Valid != 0; TableIndex++) {
    //
    // Get the 16-bit snippet code from the same Firmware Volume as this driver
    //
    LocalDataImage  = NULL;
    LocalDataSize   = 0;
    Status = GetImageEx (
               mPrivateData.ImageHandle,
               &SystemOemIntTable[TableIndex].FileName,
               EFI_SECTION_RAW,
               &LocalDataImage,
               &LocalDataSize,
               FALSE
               );

    if (!EFI_ERROR (Status)) {
      FoundLocalDataImage[LocalCount] = LocalDataImage;
      TotalSize += (UINT32) LocalDataSize;
      FoundLocalDataSize[LocalCount] = (UINT32) LocalDataSize;
      ++LocalCount;
    }
  }

  if (LocalCount == 0) {
    *DataImage  = NULL;
    *DataSize   = 0;
    *Count      = 0;
    return EFI_NOT_FOUND;
  } else {
    //
    // Concatenate snippets & add 2 for Count at start of table.
    //
    TotalSize += 2;
    Status = (gBS->AllocatePool) (
                    EfiBootServicesData,
                    TotalSize,
                    &FullTable
                    );
    ASSERT_EFI_ERROR(Status);
    if (!EFI_ERROR (Status)){
      //
      // Skip Count field
      //
      CurrentPtr = ((UINT8 *) FullTable) + 2 * sizeof (UINT8);

      for (Index = 0; Index < LocalCount; Index++) {
        CopyMem (
          (VOID *) CurrentPtr,
          FoundLocalDataImage[Index],
          (UINTN) FoundLocalDataSize[Index]
          );
        CurrentPtr = (UINT8 *) CurrentPtr + FoundLocalDataSize[Index];
      }

      *DataImage  = FullTable;
      *DataSize   = TotalSize;
      *Count      = LocalCount;
    }
    return EFI_SUCCESS;
  }
}

/**
  Load and initialize the Legacy BIOS SMM handler.

  @param[in] This                   Protocol instance pointer.
  @param[in] EfiToLegacy16BootTable Pointer to Legacy16 boot table.

  @retval    EFI_SUCCESS            SMM code loaded
  @retval    EFI_DEVICE_ERROR       SMM code failed to load
**/
EFI_STATUS
EFIAPI
SmmInit (
  IN EFI_LEGACY_BIOS_PLATFORM_PROTOCOL   *This,
  IN  VOID                               *EfiToLegacy16BootTable
  )
{
  return EFI_SUCCESS;
}

/**
  The function gets Bios version string from FirmwareVolume. After it finds the string, it returns the string's
  address and size.

  @param[in] This          Protocol instance pointer.
  @param[in] Table         Point to the buffer that saves the BIOS string
  @param[in] TableSize     Size of the BIOS string

  @retval    EFI_SUCCESS   Find the string and return it's size ,address
  @retval    EFI_NOT_FOUND
**/
EFI_STATUS
FillOem16String (
  IN EFI_LEGACY_BIOS_PLATFORM_PROTOCOL              *This,
  IN VOID                                           **Table,
  OUT UINTN                                         *TableSize
  )
{
  BIOS_ID_IMAGE                         BiosIdImage;
  CHAR8                                 *StringTable;
  EFI_STATUS                            Status;
  UINTN                                 StringLength;

  //
  // See if it has the BIOS ID file
  //
  Status = GetBiosId (&BiosIdImage);
  if (EFI_ERROR (Status)) {
    return EFI_NOT_FOUND;
  }

  StringTable = NULL;
  StringTable = AllocateZeroPool (sizeof (BIOS_ID_STRING));
  if (StringTable == NULL) {
    // If we can't allocate the buffer, assume the system doesn't have BIOSID string
    return EFI_NOT_FOUND;
  }

  StringTable = "0BIOS Version:0.00";
  StringLength = AsciiStrLen(StringTable);

  if (StringLength >= 2) {
    StringTable[StringLength - 2] = (CHAR8)(BiosIdImage.BiosIdString).VersionMajor[2];
    StringTable[StringLength - 1] = (CHAR8)(BiosIdImage.BiosIdString).VersionMajor[3];
  }

  *Table = StringTable;

  *TableSize = StringLength;

  StringTable[0] = 0;

  return EFI_SUCCESS;
}

/**
  Load and set for IVT for VBIOS hooks.

  The Video BIOS Hook Table will be handled by local function PreSetHookForVbios
  and will not be treated as normal oemIntTable being sent to LegacyBios Protocol
  For the reason that these hooks must be plugged into IVT before POST, but LegacyBios handle them during booting to OS when is too late.

    -------------------------------------------------------------------------
     ;Newly edited for a formatted asm for multiple hooks for INT15 of VBIOS

     OFFSET_MNVS    EQU     91 ; 91 is the offset of boot display device variable in MNVS
     RaxValue      EQU     05F35h;  For boot Display hook

     ;Below code has a fixed format for a hook of INT15 and only the code following "Ours:" could be modified by user.
     OemInt        DW      15h                                   (2B) 15 00
     Rax           DW      RaxValue                              (2B) 35 5F
     CodeSize      DD      (offset CodeEnd) - (offset CodeStart) (4B) 1E 00 00 00
     CodeStart:
                   jmp     Begin                                 (2B) EB 04        raw[00] ~ [01]
     NextVector    DD      ?                                     (4B) 00 00 00 00
     Begin:
                   cmp     ax, RaxValue                          (3B) 3D 35 5F
                   je      Ours                                  (2B) 74 05
     NotOurs:
                   jmp     DWORD PTR cs:[NextVector]             (5B) 2E FF 2E 0A 00  [0B] ~ [0F]
     Ours:
             ; Get the value from the MNVS area got from setup options and filled by C code
                   mov     cl, BYTE PTR [MnvsBaseAddr+OFFSET_MNVS](5B)2E 8A 0E 7D 00
                   xor     ax, ax                                (2B) 33 C0
                   mov     al, 05Fh                              (2B) B0 5F
                   iret                                          (1B) CF

     ;The address will be filled by C load code for this bin with actual value get from MNVS
     MnvsBaseAddr  DD      ?                                     (4B) 00 00 00 00
     CodeEnd:
                   CSEG    ENDS
                           END
     -------------------------------------------------------------------------

  @param[in] LegacyBios      Pointer to Legacy BIOS protocol in CSM module

  @retval    EFI_SUCCESS     Hook code loaded to IVT OK
  @retval    EFI_LOAD_ERROR  Hook code failed to load
**/
EFI_STATUS
PreSetHookForVbios (
  IN EFI_LEGACY_BIOS_PROTOCOL *LegacyBios
  )
{
  UINT32                        OldHookedPtr;
  UINT32                        TableOffset;
  UINT32                        HookPoolAddr;
  UINT16                        LegacySegment;
  UINT16                        LegacyOffset;
  UINTN                         Index;
  EFI_STATUS                    Status;
  VOID                          *IntPtr;
  VOID                          *IntOffsetPtr;
  VOID                          *IntSegmentPtr;
  EFI_OEM_INT_DATA_ELEMENT      *OemIntTableElement;
  EFI_OEM_INT_DATA              *VbiosHookTable;
  UINT32                        VbiosHookTableSize;
  UINT16                        VbiosHookNumber;
  UINT16                        MnvsOffset;
  EFI_COMPATIBILITY16_TABLE     *Legacy16Table;
  EFI_GLOBAL_NVS_AREA_PROTOCOL  *GlobalNvsArea;
  UINT32                        MnvsBaseAddr;

  //
  // Locate our shared data area
  //
  Status = gBS->LocateProtocol (
                  &gEfiGlobalNvsAreaProtocolGuid,
                  NULL,
                  (VOID **) &GlobalNvsArea
                  );
  if (!EFI_ERROR (Status)) {
    return EFI_SUCCESS;
  }

  //
  // Get all snippets and concatenate them. Output is EFI_OEM_INT_DATA
  // structure.
  //
  Status = GetOemIntDataSnippets (((VOID *) &VbiosHookTable), &VbiosHookTableSize, &VbiosHookNumber);
  if ((EFI_ERROR (Status)) || (VbiosHookNumber == 0)) {
    return EFI_LOAD_ERROR;
  }

  VbiosHookTable->Count = VbiosHookNumber;

  //
  // Skip the count field of Hook Table
  //
  TableOffset = 0;
  TableOffset += sizeof (UINT16);

  //
  // Locate free mem for IVT hooks Table from CSM--  Legacy addr - E0000 ~0xFFFFF
  //
  Legacy16Table = GetCompatibility16Table ();
  ASSERT (Legacy16Table != NULL);
  if (Legacy16Table == NULL) {
    return EFI_LOAD_ERROR;
  }

  //
  // Private->LegacyRegion->UnLock (Private->LegacyRegion, 0xE0000, 0x20000, NULL);
  //
  // Skip Lock/Unlock of E0000~FFFFF for my top caller LegacyBiosInstallRom has already done this
  //
  HookPoolAddr = (UINT32)(UINTN)Compatibility16GetTableAddressFunc (
                                  0x1, // Location 0xF0000
                                  (UINT16)VbiosHookTableSize,
                                  0,   // Alignment 2^0 = 1 byte
                                  &LegacySegment,
                                  &LegacyOffset
                                  );

  MnvsBaseAddr        = (UINT32) (UINTN) GlobalNvsArea->Area;

  OemIntTableElement  = &(VbiosHookTable->OemIntDataElement[0]);

  for (Index = 0; Index < VbiosHookNumber; Index++) {
    //
    // Get and save old interrupt vector pointer at 0:Int*4
    //
    IntOffsetPtr  = (UINT32 *) (UINTN) (4 * OemIntTableElement->Int);
    OldHookedPtr  = *(UINT32 *) IntOffsetPtr;
    //
    // Adjust IVT seg & offset
    //
    TableOffset += 2 * sizeof (UINT16) + sizeof (UINT32);
    *(UINT16 *) IntOffsetPtr  = (UINT16) (LegacyOffset + (UINT16) TableOffset);
    IntSegmentPtr             = (UINT16 *) IntOffsetPtr + 1;
    *(UINT16 *) IntSegmentPtr = ((UINT16) LegacySegment);
    //
    // Patch RawData[2] with original contents at 0:Int*4
    // See Sample RawData above in function header.
    //
    IntPtr              = (UINT32 *) &OemIntTableElement->RawData[2];
    *(UINT32 *) IntPtr  = OldHookedPtr;

    //
    // RawData[0x0E] is jmp DWORD PTR CS:[NextVector] offset
    // This is 0 based and must be patched.
    //
    IntPtr              = (UINT16 *) &OemIntTableElement->RawData[0x0E];
    *(UINT16 *) IntPtr  = (UINT16) (LegacyOffset + (UINT16) TableOffset + 2);

    //
    // Update the RetValue in the hook code by positioning MNVS area with the offset hardcoded in the bin
    //
    MnvsOffset  = *(UINT16 *) &(OemIntTableElement->RawData[0x12]);
    *(UINT16 *) &(OemIntTableElement->RawData[0x15]) = *(UINT16 *) (UINTN) (MnvsBaseAddr + MnvsOffset);

    //
    // Update TableOffset to give number of bytes from start of
    // table to next EFI_OEM_INT_DATA_ELEMENT.
    //
    TableOffset += OemIntTableElement->RawDataLength;
    //
    // Update IntPtr to point to next EFI_OEM_INT_DATA_ELEMENT
    // and set OemIntTableElement to IntPtr.
    //
    IntPtr              = (UINT8 *) OemIntTableElement + 2 * sizeof (UINT16) + sizeof (UINT32) + OemIntTableElement->RawDataLength;

    OemIntTableElement  = (EFI_OEM_INT_DATA_ELEMENT *) IntPtr;
  }
  //
  // Copy Hooks table to final location
  //
  CopyMem (
    (VOID *) (UINTN) HookPoolAddr,
    (VOID *) VbiosHookTable,
    (UINTN) VbiosHookTableSize
    );

  Legacy16Table->OemIntSegment  = LegacySegment;
  Legacy16Table->OemIntOffset   = LegacyOffset;

  return EFI_SUCCESS;
}

/**

  @retval TRUE
  @retval FALSE
**/
BOOLEAN
HighPriority (
  DEVICE_STRUCTURE   *Left,
  DEVICE_STRUCTURE   *Right
  )
{
  return (BOOLEAN) (V_PCH_INTEL_VENDOR_ID == Left->Vid &&
                    IS_PCH_SATA_DEVICE_ID (Left->Did)
                    );
}


/**
  Return the Legacy16 policy for which device should be the VGA controller
  used during a Legacy16 boot.

  @param[in]  This               Protocol instance pointer.
  @param[in]  Mode               Subfunction.
  @param[in]  Type               Handle modifier. Mode specific.
  @param[out] HandleBuffer       Buffer of all handles meeting requirement in priority.
                                    HandleBuffer[0] is highest priority.
  @param[out] HandleCount        Number of handles in buffer.
  @param[out] AdditionalData     Mode specific.

  @retval     EFI_SUCCESS        Handle is valid
  @retval     EFI_UNSUPPORTED    Mode is not supported by this platform.
  @retval     EFI_NOT_FOUND      The handle is not known
**/
EFI_STATUS
EFIAPI
GetPlatformHandle (
  IN EFI_LEGACY_BIOS_PLATFORM_PROTOCOL                     *This,
  IN EFI_GET_PLATFORM_HANDLE_MODE                          Mode,
  IN UINT16                                                Type,
  OUT EFI_HANDLE                                           **HandleBuffer,
  OUT UINTN                                                *HandleCount,
  OUT VOID                                        OPTIONAL **AdditionalData
  )
{
  DEVICE_STRUCTURE        LocalDevice[0x40];
  UINT32                  LocalIndex;
  UINT32                  Index;
  DEVICE_STRUCTURE        TempDevice;
  EFI_STATUS              Status;
  EFI_PCI_IO_PROTOCOL     *PciIo;
  UINTN                   Segment;
  UINTN                   Bus;
  UINTN                   Device;
  UINTN                   Function;
  HDD_INFO                *HddInfo;
  PCI_TYPE01              Pci;
  UINT32                  HddIndex;
  EFI_HANDLE              PciHandle;
  EFI_HANDLE              AgpHandle;
  SA_SETUP                SaSetup;
  UINTN                   VarSize;

  LocalIndex  = 0x00;
  HddInfo     = NULL;
  HddIndex    = 0x00;

  switch (Mode) {
  case EfiGetPlatformVgaHandle:
    if (mVgaHandleValid && mEnableCachedPlatformHandle) {
      *HandleBuffer   = &mVgaHandles[0];
      *HandleCount    = 1;
      return EFI_SUCCESS;
    }

    PciHandle   = NULL;
    AgpHandle   = NULL;

    FindAllDeviceTypes (0x00, 0x01, &LocalDevice[0], (UINT16 *) &LocalIndex, FALSE);
    FindAllDeviceTypes (0x03, 0x00, &LocalDevice[LocalIndex], (UINT16 *) &LocalIndex, FALSE);
    if (LocalIndex == 0) {
      return EFI_NOT_FOUND;
    }

    for (Index = 0; Index < LocalIndex; Index++) {
      //
      //  mVgaHandles[Index] = LocalDevice[Index].Handle;
      //
      Status = gBS->HandleProtocol (
                      LocalDevice[Index].Handle,
                      &gEfiPciIoProtocolGuid,
                      (VOID **) &PciIo
                      );

      if (!EFI_ERROR (Status)) {
        Status = PciIo->Pci.Read (
                              PciIo,
                              EfiPciIoWidthUint32,
                              0,
                              sizeof (Pci) / sizeof (UINT32),
                              &Pci
                              );
        if (EFI_ERROR (Status)) {
          continue;
        }
        //
        // Here we decide which VGA device to enable in PCI bus
        // The first plugin PCI VGA card device will be present as PCI VGA
        // The onchip AGP or AGP card will be present as AGP VGA
        //
        if (!IS_PCI_VGA (&Pci)) {
          continue;
        }

        PciIo->GetLocation (PciIo, &Segment, &Bus, &Device, &Function);
        if ((Bus >= 1) && (PciHandle == NULL)) {
          PciHandle = LocalDevice[Index].Handle; /* For PEG/PCH PCI Gfx */
        }

        if (Bus < 1) {
          AgpHandle = LocalDevice[Index].Handle; /* For Integrated Gfx */
        }
      }
    }

    if (PciHandle == NULL) {
      mVgaHandles[0]  = AgpHandle; /* Force it to Integrated Gfx */
    } else {
//
// "VBIOS" variable was introduced from EDK BIOS (the previous version of EDKII).
// EDK BIOS core DeviceManager module created this variable to control platform video policy.
// It has 2 possible values: 0 means external PCI, 1 means IGD.
// EDKII equivalent for "VBIOS" variable is "PrimaryDisplay" which has more possible values than "VBIOS" variable.
//
      mVgaHandles[0] = PciHandle; /* For PEG/PCH PCI Gfx */

      VarSize = sizeof (SA_SETUP);
      Status = gRT->GetVariable (L"SaSetup", &gSaSetupVariableGuid, NULL, &VarSize, &SaSetup);
      if (!EFI_ERROR(Status)) {
        if (SaSetup.PrimaryDisplay == 0 || SaSetup.PrimaryDisplay == 4) { /* PrimaryDisplay = IGFX/SG */
          mVgaHandles[0] = AgpHandle; /* Force it to IGFX for Integrated Gfx */
        }
      }
    }
    *HandleBuffer   = &mVgaHandles[0];
    *HandleCount    = 1;

    mVgaHandleValid = TRUE;
    return EFI_SUCCESS;

  case EfiGetPlatformIsaBusHandle:
    if (mIsaHandleValid && mEnableCachedPlatformHandle) {
      *HandleBuffer = &mIsaHandles[0];
      *HandleCount  = mIsaHandleCount;
      return EFI_SUCCESS;
    }

    //
    // Locate all found block io devices
    //
    FindAllDeviceTypes (
      (UINT8) PCI_CLASS_BRIDGE,
      (UINT8) PCI_CLASS_BRIDGE_ISA_PDECODE,
      (DEVICE_STRUCTURE *) (&LocalDevice[0]),
      (UINT16 *) (&LocalIndex),
      1
      );

    FindAllDeviceTypes (
      (UINT8) PCI_CLASS_BRIDGE,
      (UINT8) PCI_CLASS_BRIDGE_ISA,
      (DEVICE_STRUCTURE *) (&LocalDevice[LocalIndex]),
      (UINT16 *) (&LocalIndex),
      1
      );
    if (LocalIndex == 0) {
      return EFI_NOT_FOUND;
    }

    for (Index = 0; Index < LocalIndex; Index++) {
      if (V_PCH_INTEL_VENDOR_ID == LocalDevice[Index].Vid) {
        TempDevice          = LocalDevice[0];
        LocalDevice[0]      = LocalDevice[Index];
        LocalDevice[Index]  = TempDevice;
      }
    }

    for (Index = 0; Index < LocalIndex; Index++) {
      mIsaHandles[Index] = LocalDevice[Index].Handle;
    }

    *HandleBuffer = &mIsaHandles[0];
    *HandleCount  = LocalIndex;

    mIsaHandleCount = LocalIndex;
    mIsaHandleValid  = TRUE;
    return EFI_SUCCESS;

  case EfiGetPlatformUsbHandle:
  default:
    return EFI_UNSUPPORTED;
  }
}

/**
  Wait for the KBC input buffer empty.

  @retval  TRUE   The KBC input buffer is empty
  @retval  FALSE  Timeout happens
**/
BOOLEAN
KbcWaitForInputBufferEmpty (
  VOID
  )
{
  EFI_STATUS                            Status;
  EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL       *PciRootBridgeIo;
  UINT8                                 KbcStatus;
  UINTN                                 Timeout;

  PciRootBridgeIo = mPrivateData.PciRootBridgeIo;
  KbcStatus       = 0;
  Timeout         = 0;

  Status = PciRootBridgeIo->Io.Read (
                                  PciRootBridgeIo,
                                  EfiPciWidthUint8,
                                  KBC_C_PORT,
                                  1,
                                  (VOID *) &KbcStatus
                                  );
  if (EFI_ERROR (Status)) {
    return FALSE;
  }

  while (((KbcStatus & KBC_STATUS_IBF) != 0) && (Timeout < 0x20000)) {
    gBS->Stall (15);
    Timeout++;
    Status = PciRootBridgeIo->Io.Read (
                                    PciRootBridgeIo,
                                    EfiPciWidthUint8,
                                    KBC_C_PORT,
                                    1,
                                    (VOID *) &KbcStatus
                                    );
    if (EFI_ERROR (Status)) {
      return FALSE;
    }
  }

  return (Timeout < 0x20000) ? TRUE : FALSE;
}


/**
  Wait for the KBC output buffer to be full.

  @retval  TRUE   The KBC output buffer is full
  @retval  FALSE  Timeout happens
**/
BOOLEAN
KbcWaitForOutputBufferFull (
  VOID
  )
{
  EFI_STATUS                            Status;
  EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL       *PciRootBridgeIo;
  UINT8                                 KbcStatus;
  UINTN                                 Timeout;

  PciRootBridgeIo = mPrivateData.PciRootBridgeIo;
  KbcStatus       = 0;
  Timeout         = 0;

  Status = PciRootBridgeIo->Io.Read (
                                  PciRootBridgeIo,
                                  EfiPciWidthUint8,
                                  KBC_C_PORT,
                                  1,
                                  (VOID *) &KbcStatus
                                  );
  if (EFI_ERROR (Status)) {
    return FALSE;
  }

  while (((KbcStatus & KBC_STATUS_OBF) == 0) && (Timeout < 0x20000)) {
    gBS->Stall (15);
    Timeout++;
    Status = PciRootBridgeIo->Io.Read (
                                    PciRootBridgeIo,
                                    EfiPciWidthUint8,
                                    KBC_C_PORT,
                                    1,
                                    (VOID *) &KbcStatus
                                    );
    if (EFI_ERROR (Status)) {
      return FALSE;
    }
  }

  return (Timeout < 0x20000) ? TRUE : FALSE;
}


/**
  Send an command to KBC command port.

  @param[in] Command  KBC Command

  @retval    TRUE     Send command successfully
  @retval    FALSE    Timeout happens
**/
BOOLEAN
KbcSendCommand (
  IN UINT8                              Command
  )
{
  EFI_STATUS                            Status;
  EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL       *PciRootBridgeIo;
  BOOLEAN                               b;

  PciRootBridgeIo = mPrivateData.PciRootBridgeIo;
  b               = KbcWaitForInputBufferEmpty ();
  if (b) {
    Status = PciRootBridgeIo->Io.Write (
                                    PciRootBridgeIo,
                                    EfiPciWidthUint8,
                                    KBC_C_PORT,
                                    1,
                                    (VOID *) &Command
                                    );
    if (EFI_ERROR (Status)) {
      b = FALSE;
    }
  }

  return b;
}


/**
  Send data to KBC data port.

  @param[in] Data    KBC Data

  @retval    TRUE    Send data successfully
  @retval    FALSE   Timeout happens
**/
BOOLEAN
KbcSendData (
  IN UINT8                              Data
  )
{
  EFI_STATUS                            Status;
  EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL       *PciRootBridgeIo;
  BOOLEAN                               b;

  PciRootBridgeIo = mPrivateData.PciRootBridgeIo;
  b               = KbcWaitForInputBufferEmpty ();
  if (b) {
    Status = PciRootBridgeIo->Io.Write (
                                    PciRootBridgeIo,
                                    EfiPciWidthUint8,
                                    KBC_D_PORT,
                                    1,
                                    (VOID *) &Data
                                    );
    if (EFI_ERROR (Status)) {
      b = FALSE;
    }
  }

  return b;
}


/**
  Read data from KBC data port.

  @param[in, out] Data   Data return buffer

  @retval         TRUE   Get data successfully
  @retval         FALSE  Timeout happens
**/
BOOLEAN
KbcReadData (
  IN OUT UINT8                          *Data
  )
{
  EFI_STATUS                            Status;
  EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL       *PciRootBridgeIo;
  BOOLEAN                               b;

  PciRootBridgeIo = mPrivateData.PciRootBridgeIo;
  b               = KbcWaitForOutputBufferFull ();
  if (b) {
    Status = PciRootBridgeIo->Io.Read (
                                    PciRootBridgeIo,
                                    EfiPciWidthUint8,
                                    KBC_D_PORT,
                                    1,
                                    (VOID *) Data
                                    );
    if (EFI_ERROR (Status)) {
      b = FALSE;
    }
  }

  return b;
}


/**
  Enable KBC's keyboard interrupt by setting the bit in the KBC's Command Byte for
  USB keyboard legacy support.

**/
VOID
EnableKbcKeyboardInterruptForUsbKeyboardLegacySupport (
  VOID
  )
{
  EFI_STATUS                            Status;
  EFI_HANDLE                            *HandleBuffer;
  UINTN                                 HandleCount;
  EFI_USB_IO_PROTOCOL                   *UsbIo;
  UINTN                                 Index;
  UINT8                                 Data8;
  BOOLEAN                               b;

  if (!mUsbKeyboardPresent) {
    //
    // Get SimpleTextIn and find Usb controller
    //
    Status = gBS->LocateHandleBuffer (
                    ByProtocol,
                    &gEfiSimpleTextInProtocolGuid,
                    NULL,
                    &HandleCount,
                    &HandleBuffer
                    );
    if (EFI_ERROR (Status)) {
      return;
    }

    for (Index = 0; Index < HandleCount; Index++) {
      //
      // Find UsbIo protocol to identify the Usb controller
      //
      Status = gBS->HandleProtocol (
                      HandleBuffer[Index],
                      &gEfiUsbIoProtocolGuid,
                      (VOID **) &UsbIo
                      );

      if (!EFI_ERROR (Status)) {
        mUsbKeyboardPresent = TRUE;
        break;
      }
    }
    (gBS->FreePool) (HandleBuffer);
  }

  if (mUsbKeyboardPresent) {
    //
    // Get current KBC's Command Byte
    //
    b = KbcSendCommand (KEYBOARD_CONTROLLER_COMMAND_BYTE_READ);
    if (!b) {
      return;
    }
    b = KbcReadData (&Data8);
    if (!b) {
      return;
    }
    //
    // If keyboard interrupt and keyboard are already enabled, do nothing and return.
    //
    if (((Data8 & KEYBOARD_INTERRUPT_ENABLE) != 0) &&
        ((Data8 & KEYBOARD_DISABLE) == 0)
      ) {
      return;
    }
    //
    // Set keyboard interrupt enabled and clear keyboard disabled.
    //
    b = KbcSendCommand (KEYBOARD_CONTROLLER_COMMAND_BYTE_WRITE);
    if (!b) {
      return;
    }
    Data8 &= (~KEYBOARD_DISABLE);
    Data8 |= KEYBOARD_INTERRUPT_ENABLE;
    b = KbcSendData (Data8);
    if (!b) {
      return;
    }
  }
}


/**
  Return the Legacy16 policy for which device should be the VGA controller
  used during a Legacy16 boot.

  @param[in]      This                 Protocol instance pointer.
  @param[in]      Mode                 Subfunction.
  @param[in]      Type                 Handle modifier. Mode specific.
  @param[in]      DeviceHandle         Handle of device OPROM is associated with.
  @param[in, out] Shadowaddress        Address that OPROM is shadowed at prior to
                                       initialization or next available free OPROM address.
  @param[in]      Compatibility16Table Pointer to the Compatibility16Table.
  @param[out]     AdditionalData       Mode specific.

  @retval         EFI_SUCCESS          Handle is valid
  @retval         EFI_UNSUPPORTED      Mode is not supported by this platform.
  @retval         EFI_NOT_FOUND        The handle is not known
**/
EFI_STATUS
EFIAPI
PlatformHooks (
  IN EFI_LEGACY_BIOS_PLATFORM_PROTOCOL              *This,
  IN EFI_GET_PLATFORM_HOOK_MODE                     Mode,
  IN UINT16                                         Type,
  IN EFI_HANDLE                                     DeviceHandle,          OPTIONAL
  IN OUT UINTN                                      *Shadowaddress,        OPTIONAL
  IN EFI_COMPATIBILITY16_TABLE                      *Compatibility16Table, OPTIONAL
  OUT VOID                                          **AdditionalData       OPTIONAL
  )
{
  EFI_LEGACY_BIOS_PROTOCOL      *LegacyBios;
  EFI_STATUS                    Status;
  UINT8                         *RomAddress;
  BOOLEAN                       LightAhciRom;
  UINTN                         FilenameIndex;
  VOID                          *LocalRomImage;
  UINTN                         LocalRomSize;
  UINTN                         Flags;
  EFI_PCI_IO_PROTOCOL           *AhciPciIo;
  UINT16                        VendorId;
  UINT16                        DeviceId;
  UINTN                         PciSegment;
  UINTN                         PciBus;
  UINTN                         PciDevice;
  UINTN                         PciFunction;
  WDT_PROTOCOL                  *WdtProtocol;
  EFI_PCI_IO_PROTOCOL           *PciIo;
  UINT8                         Class;
  UINT8                         SubClass;
#ifdef AMT_SUPPORT
  AMT_WRAPPER_PROTOCOL          *AmtWrapper;
  AMT_POLICY_PROTOCOL           *DxeAmtPolicy;
  UINTN                         VariableSize;
  ME_BIOS_EXTENSION_SETUP       MeBiosExtensionSetupData;
#endif
  SETUP_DATA                    SystemConfiguration;
  PCH_SETUP                     PchSetup;
  UINTN                         VarSize;
  PLATFORM_INFO                 *PlatformInfo;

  //
  // Try to hook the AHCI related oprom
  //
  if (DeviceHandle != 0) {
    PlatformHookInitAhciOprom (DeviceHandle);
    Status = gBS->HandleProtocol (
                    DeviceHandle,
                    &gEfiPciIoProtocolGuid,
                    (VOID **) &AhciPciIo
                    );
    if (!EFI_ERROR (Status)) {
      AhciPciIo->Pci.Read (AhciPciIo, EfiPciIoWidthUint16, 0, 1, &VendorId);
      AhciPciIo->Pci.Read (AhciPciIo, EfiPciIoWidthUint16, 2, 1, &DeviceId);

      if ((VendorId == V_PCH_INTEL_VENDOR_ID) &&
          (IS_PCH_SATA_AHCI_DEVICE_ID (DeviceId) ||
          IS_PCH_SATA_RAID_DEVICE_ID (DeviceId))
         ) {

        //
        // Sata controller works at AHCI or RAID mode and LegacyAhci/LegacyRaid rom should be loaded.
        //
        mLegacyAhciRom = TRUE;
      }
    }
  }
  Status = gBS->LocateProtocol (
                  &gPlatformInfoProtocolGuid,
                  NULL,
                  (VOID **) &PlatformInfo
                  );
  ASSERT_EFI_ERROR (Status);

  Status = gBS->LocateProtocol (
                  &gEfiLegacyBiosProtocolGuid,
                  NULL,
                  (VOID **) &LegacyBios
                  );
  ASSERT_EFI_ERROR (Status);

  switch (Mode) {
  case EfiPlatformHookPrepareToScanRom:
    VarSize = sizeof (SETUP_DATA);
    Status = gRT->GetVariable (
                    L"Setup",
                    &gSetupVariableGuid,
                    NULL,
                    &VarSize,
                    &SystemConfiguration
                    );
    ASSERT_EFI_ERROR (Status);

    VarSize = sizeof (PCH_SETUP);
    Status = gRT->GetVariable (
                    L"PchSetup",
                    &gPchSetupVariableGuid,
                    NULL,
                    &VarSize,
                    &PchSetup
                    );
    ASSERT_EFI_ERROR (Status);
    Status = gBS->HandleProtocol (DeviceHandle, &gEfiPciIoProtocolGuid, (VOID **) &PciIo);
    ASSERT_EFI_ERROR (Status);
    //
    // Install OPROM will come here.
    //
    if (DeviceHandle != NULL) {
      Status = PciIo->Pci.Read (PciIo, EfiPciIoWidthUint8, 0xB, 1, &Class);
      Status = PciIo->Pci.Read (PciIo, EfiPciIoWidthUint8, 0xA, 1, &SubClass);
      ASSERT_EFI_ERROR (Status);
      if ((Class == PCI_CLASS_MASS_STORAGE) &&
          (SubClass == PCI_CLASS_MASS_STORAGE_RAID)) {
        if (PchSetup.SataLegacyOrom == 0) {
          return EFI_UNSUPPORTED;
        }
      }
    }
    //
    // Check for Thunderbolt(TM) Support
    //
    if (SystemConfiguration.TbtSupport && SystemConfiguration.TbtSkipPciOprom) {
    //
    // Bridge Check Should be done only once .
    //
      if (!mBridgeCheckDone) {
        CheckForThunderboltBridge(&gSecondaryBus,&gSubordinateBus);
        DEBUG ((DEBUG_INFO, "Thunderbolt(TM) (TbtSkipPciOprom) : Selected SecondaryBus = 0x%x \n", gSecondaryBus));
        DEBUG ((DEBUG_INFO, "Thunderbolt(TM) (TbtSkipPciOprom) : Selected SubordinateBus = 0x%x \n", gSubordinateBus));
      }
      if (gSecondaryBus != 0 && gSubordinateBus != 0) {
        PciIo->GetLocation (
                 PciIo,
                 &PciSegment,
                 &PciBus,
                 &PciDevice,
                 &PciFunction
                 );
        //
        // Check if OpRom is on Device which is under Thunderbolt(TM) Bridge
        // if so, Skip installing OpRom.
        //
        if ((PciBus >= gSecondaryBus) && (PciBus <= gSubordinateBus)) {
          DEBUG ((DEBUG_INFO, "OpRom. found at Thunderbolt(TM) endpoint, Skip installing\n"));
          return EFI_UNSUPPORTED;
        }
      }
    }
    LightAhciRom = FALSE;
    if (mLegacyAhciRom) {
      //
      // Check if it is light version of AHCI ROM
      //
      for (RomAddress = (UINT8 *)(UINTN)*Shadowaddress; RomAddress < (UINT8 *)(UINTN)*Shadowaddress + 0x100; RomAddress++) {
        if (*(UINT32 *) RomAddress == LIGHT_AHCI_ROM_SIGNATURE) {
          //
          // It is light version of AHCI OpRom.
          //
          DEBUG ((DEBUG_INFO, "This is light version of AHCI ROM!\n"));
          LightAhciRom = TRUE;
          break;
        }
      }
    }

    if (!LightAhciRom) {
      PrepareToScanRomHookVideo (DeviceHandle, mVgaHandles[0]);
    }

    //
    // 0. Stop WDT and Enable Legacy Serial Redirection to align with Greens Glacier
    //
    {
      //
      // Stop Icc Watch Dog before entering MEBx Setup
      //
      Status = gBS->LocateProtocol (&gWdtProtocolGuid, NULL, (VOID **) &WdtProtocol);
      if (!EFI_ERROR (Status)) {
        mWdtEnabled = WdtProtocol->IsWdtEnabled ();
        if (mWdtEnabled == TRUE) {
          WdtProtocol->Disable ();
        }
      }

#ifdef AMT_SUPPORT
      if (DeviceHandle != NULL) {
        Status = gBS->HandleProtocol (DeviceHandle, &gEfiPciIoProtocolGuid, (VOID **) &PciIo);
        ASSERT_EFI_ERROR (Status);
        Status = PciIo->Pci.Read (PciIo, EfiPciIoWidthUint8, 0xB, 1, &Class);
        ASSERT_EFI_ERROR (Status);
      } else {
        //
        // Assign to a value other than PCI_CL_DISPLAY
        //
        Class = PCI_CLASS_DISPLAY + 1;
      }
      ///
      /// Check if Amt Policy present and get MEBx Sync-up Data
      ///
      Status = gBS->LocateProtocol (&gDxeAmtPolicyGuid, NULL, (VOID **) &DxeAmtPolicy);
      if (!EFI_ERROR (Status)) {
        VariableSize = sizeof (MeBiosExtensionSetupData);
        Status = gRT->GetVariable (
                        gMeBiosExtensionSetupName,
                        &gMeBiosExtensionSetupGuid,
                        NULL,
                        &VariableSize,
                        &MeBiosExtensionSetupData
                        );
        if (!EFI_ERROR (Status)) {
          if (Class != PCI_CLASS_DISPLAY && ((MeBiosExtensionSetupData.AmtSol & SOL_ENABLE) != 0)) {
            mSolEnabled = TRUE;
          }
        }
      }
#endif
    }

    if (mLegacyAhciRom) {
      PrepareToScanRomHookAhciOprom (DeviceHandle);
    }

    //
    // Set the 80x25 Text VGA Mode
    //
    if (!LightAhciRom) {
      PrepareToScanRomHookVideo (DeviceHandle, mVgaHandles[0]);
      PrepareToScanRomHookSetTextMode ();
    }

    //
    // Set platform specific INT15h hooks for VBIOS
    //
    PreSetHookForVbios (LegacyBios);

    DEBUG ((DEBUG_INFO, "EfiPlatformHookPrepareToScanRom\n"));

    PrepareToScanRomHookEbdaBcv (DeviceHandle);

    EnableKbcKeyboardInterruptForUsbKeyboardLegacySupport ();

    return Status;
    break;

  case EfiPlatformHookShadowServiceRoms:
    for (FilenameIndex = 0; ServiceRomTable[FilenameIndex].Valid; FilenameIndex++) {
      //
      // See if we get the 16-bit service rom code from this Firmware Volume
      //
      LocalRomImage = NULL;
      LocalRomSize  = 0;
      Status = GetImageEx (
                 mPrivateData.ImageHandle,
                 &ServiceRomTable[FilenameIndex].FileName,
                 EFI_SECTION_RAW,
                 &LocalRomImage,
                 &LocalRomSize,
                 FALSE
                 );

      if (LocalRomImage != NULL || LocalRomSize != 0) {

        LegacyBios->InstallPciRom (
                      LegacyBios,
                      NULL,
                      &LocalRomImage,
                      &Flags,
                      NULL,
                      NULL,
                      NULL,
                      NULL
                      );
      }
    }

    return EFI_SUCCESS;
    break;

  case EfiPlatformHookAfterRomInit:
    AfterRomInitHookVideo (DeviceHandle, mVgaHandles[0]);

    {
#ifdef AMT_SUPPORT
      if (mSolEnabled) {
        mSolEnabled = FALSE;
      }
#endif

      Status = gBS->LocateProtocol (&gWdtProtocolGuid, NULL, (VOID **) &WdtProtocol);
      if (!EFI_ERROR (Status)) {
        if (mWdtEnabled) {
          WdtProtocol->ReloadAndStart (WDT_TIMEOUT);
        }
      }

#ifdef AMT_SUPPORT
      Status = gBS->LocateProtocol (&gEfiAmtWrapperProtocolGuid, NULL, (VOID **) &AmtWrapper);
      if (!EFI_ERROR (Status)) {
        AmtWrapper->AmtWrapperSet (SET_BIOS_WDT_START, 0);
      }
#endif
    }

    if (mLegacyAhciRom) {
      AfterRomInitHookAhciOprom (DeviceHandle);
    }

    AfterRomInitHookEbdaBcv (DeviceHandle, Shadowaddress);

    return EFI_SUCCESS;
    break;

  default:
    return EFI_UNSUPPORTED;
    break;
  }
}


/**
  Gets the following information:
    1. List of IRQ routing entries and number of entries.
    2. Pointer to Entire $PIR table and length.
    3. List of IRQs to assign to PCI in priority.

  @param[in]  This                      Protocol instance pointer.
  @param[out] RoutingTable              Pointer to PCI IRQ Routing table.
  @param[out] RoutingTableEntries       Number of entries in table.
  @param[out] LocalPirqTable            $PIR table
  @param[out] PirqTableSize             $PIR table size
  @param[out] LocalIrqPriorityTable     List of interrupts in priority order to assign
  @param[out] IrqPriorityTableEntries   Number of entries in priority table

  @retval     EFI_SUCCESS               Table pointer returned
**/
EFI_STATUS
EFIAPI
GetRoutingTable (
  IN EFI_LEGACY_BIOS_PLATFORM_PROTOCOL            * This,
  OUT VOID                                        **RoutingTable,
  OUT UINTN                                       *RoutingTableEntries,
  OUT VOID                                        **LocalPirqTable, OPTIONAL
  OUT UINTN                                       *PirqTableSize, OPTIONAL
  OUT VOID                                        **LocalIrqPriorityTable, OPTIONAL
  OUT UINTN                                       *IrqPriorityTableEntries OPTIONAL
  )
{
  UINTN       TableEntries;
  VOID        *PirqTable;
  UINTN       TableSize;
  EFI_STATUS  Status;

  DEBUG ((DEBUG_INFO, "GetRoutingTable\n"));

  Status = mPrivateData.IrqTableInfo->Initialize (mPrivateData.IrqTableInfo, mPrivateData.PciAutoDetect);
  ASSERT_EFI_ERROR (Status);

  Status = mPrivateData.IrqTableInfo->GetPirqTable (mPrivateData.IrqTableInfo, &PirqTable);
  ASSERT_EFI_ERROR (Status);

  TableSize = ((EFI_LEGACY_PIRQ_TABLE_HEADER *)PirqTable)->TableSize;

  TableEntries = (TableSize - sizeof (EFI_LEGACY_PIRQ_TABLE_HEADER)) / sizeof (EFI_LEGACY_IRQ_ROUTING_ENTRY);

  if (RoutingTable != NULL) {
    *RoutingTable         = (VOID *)((UINTN)PirqTable + sizeof (EFI_LEGACY_PIRQ_TABLE_HEADER));
    *RoutingTableEntries  = TableEntries;
  }

  if (LocalPirqTable != NULL) {
    *LocalPirqTable    = PirqTable;
    *PirqTableSize     = TableSize;
  }

  if (LocalIrqPriorityTable != NULL) {
    *LocalIrqPriorityTable    = IrqPriorityTable;
    *IrqPriorityTableEntries  = MAX_IRQ_PRIORITY_ENTRIES;
  }

  return EFI_SUCCESS;
}


/**
  Creates and populates the MP table.
  This function is intended to be a member of
  EFI_LEGACY_BIOS_PLATFORM_PROTOCOL.
  This function is expected to be called twice:
  On the first call the LegacySegment and LegacyOffset have to be zero.
  The function creates the tables and returns the size of the tables in bytes.
  Then, on the second call the LegacySegment and LegacyOffset point to the
  16-bit address where the table will be copied. At that time this function
  will have its chance to fix anything in the table itself.
  Things that needed to be fixed are the internal pointers and checksums.

  @param[in]  This              Driver context.
  @param[out] Table             Pointer to MP table.
  @param[out] TableSize         Size of the table in bytes.
  @param[out] Location          Legacy region requested
                                0x00 = Any location
                                Bit 0 = 0xF0000 region
                                Bit 1 = 0xE0000 region
                                Multiple bits can be set
  @param[out] Alignment         Address alignment for allocation.
                                Bit mapped. First non-zero bit from right
                                is alignment.
  @param[in]  LegacySegment     Segment where EfiCompatibility code will place MP table.
  @param[in]  LegacyOffset      Offset where EfiCompatibility code will place MP table.

  @retval     EFI_SUCCESS       Table construction is successfull.
  @retval     EFI_UNSUPPORTED   Table not supported or this platform is not an MP platform.
  @retval     Other             Error occured constructing the table.
**/
EFI_STATUS
GetMpTable (
  IN  EFI_LEGACY_BIOS_PLATFORM_PROTOCOL *This,
  OUT VOID                              **Table,
  OUT UINTN                             *TableSize,
  OUT UINTN                             *Location,
  OUT UINTN                             *Alignment,
  IN  UINT16                            LegacySegment,
  IN  UINT16                            LegacyOffset
  )
{
  EFI_STATUS    Status;
  VOID          *MpsTable;

  Status = mPrivateData.IrqTableInfo->Initialize (mPrivateData.IrqTableInfo, mPrivateData.PciAutoDetect);
  ASSERT_EFI_ERROR (Status);

  Status = mPrivateData.IrqTableInfo->GetMpTable (mPrivateData.IrqTableInfo, &MpsTable);
  if (EFI_ERROR (Status)) {
    return EFI_UNSUPPORTED;
  }

  //
  // Must be alligned at 16-byte per MP table spec.
  //
  *Alignment = 0x10;

  //
  // Allocate at E segment.
  //
  *Location = 0x2;

  //
  // Return table address and size
  //
  *Table     = MpsTable;
  *TableSize = sizeof(EFI_LEGACY_MP_TABLE_FLOATING_POINTER);

  return EFI_SUCCESS;
}


/**
  Gets the MP Table information:

  @param[in]  This                     Protocol instance pointer.
  @param[in]  Mode                     Mode.
  @param[out] Table                    Pointer to table or binary.
  @param[out] TableSize                Size in bytes of table or binary.
  @param[out] Location                 Legacy region requested
                                       0x00 = Any location
                                       Bit 0 = 0xF0000 region
                                       Bit 1 = 0xE0000 region
                                       Multiple bits can be set
  @param[out] Alignment                Address alignment for allocation.
                                       Bit mapped. First non-zero bit from right
                                       is alignment.
  @param[in]  LegacySegment            Segment in LegacyBios where table or binary
                                       is stored
  @param[in]  LegacyOffset             Offset in LegacyBios where table or binary
                                       is stored
    Note: Depending upon Mode this function may be called twice. First time
          LegacySegment & Offset are 0 so size can be determined and second
          with LegacySegment & Offset != 0 so table fix ups can be
          performed. Size CANNOT change between the two calls.

  @retval     EFI_SUCCESS             Table pointer returned
  @retval     EFI_UNSUPPORTED         Table not supported or this platform
  @retval     EFI_NOT_FOUND           Binary not found.
**/
EFI_STATUS
EFIAPI
GetPlatformInfo (
  IN EFI_LEGACY_BIOS_PLATFORM_PROTOCOL            *This,
  IN EFI_GET_PLATFORM_INFO_MODE                   Mode,
  OUT VOID                                        **Table,
  OUT UINTN                                       *TableSize,
  OUT UINTN                                       *Location,
  OUT UINTN                                       *Alignment,
  IN  UINT16                                      LegacySegment,
  IN  UINT16                                      LegacyOffset
  )
{
  EFI_STATUS                    Status;
  UINTN                         TableIndex;
  VOID                          *LocalTable;
  UINTN                         LocalTableSize;

  switch (Mode) {
  case EfiGetPlatformBinarySystemRom:
    //
    // Loop through table of video option rom descriptions
    //
    for (TableIndex = 0; SystemRomTable[TableIndex].Valid != 0; TableIndex++) {
      //
      // Get the 16-bit Video BIOS code from the same Firmware Volume as this driver
      //
      LocalTable      = NULL;
      LocalTableSize  = 0;
      Status = GetImageEx (
                 mPrivateData.ImageHandle,
                 &SystemRomTable[TableIndex].FileName,
                 EFI_SECTION_RAW,
                 &LocalTable,
                 &LocalTableSize,
                 FALSE
                 );

      if (!EFI_ERROR (Status)) {
        *Table      = LocalTable;
        *TableSize  = (UINTN) LocalTableSize;
        return EFI_SUCCESS;
      }
    }

    return EFI_NOT_FOUND;

  case EfiGetPlatformBinaryOem16Data:
    // First, we need to request a buffer that is located in F-Segment. If LegacySegment and LegacyOffset are 0,
    // returns TableSize, Location and Alignment that we need. LegacyBios calls CSM16 to find a free space and
    // return the free space's address back. Then, we find the BIOS version from FirmwareVolume and return the
    // string's address and size. LegacyBios has the responsilbility to copy the string to the free space that
    // is located in F-Segment.
    //
    if ((LegacySegment == 0) && (LegacyOffset == 0)) {
      //
      //The first call, return TableSize, Location and Alignment
      //The string should be put in F-Segment, set bit0 of Location
      //
      *TableSize = MAX_BIOS_VERSION_SIZE;
      *Table = NULL;
      *Location = 0x01;
      *Alignment = 0;
    } else {
      //
      //The second call, return the address for the string
      //
      *Table = (VOID *) (UINTN) ((LegacySegment << 4) + LegacyOffset);
      ZeroMem (*Table, MAX_BIOS_VERSION_SIZE);
      *Table = NULL;
      Status = FillOem16String(This, Table, TableSize);
      if (EFI_ERROR (Status)) {
        return EFI_NOT_FOUND;
      }
    }
    return EFI_SUCCESS;
    break;

  case EfiGetPlatformBinaryMpTable:
    Status = GetMpTable (
              This,
              Table,
              TableSize,
              Location,
              Alignment,
              LegacySegment,
              LegacyOffset
              );
    return Status;
    break;

  //
  // enum in IntelFrameworkPkg/Include/Protocol/LegacyBiosPlatform.h
  //
  case EfiGetPlatformEndOpromShadowAddr:
    *Location = 0xE7FFF;
    return EFI_SUCCESS;
    break;

  case EfiGetPlatformBinaryOemIntData:
  case EfiGetPlatformBinaryOem32Data:
  case EfiGetPlatformBinaryTpmBinary:
  case EfiGetPlatformPciExpressBase:
  default:
    return EFI_UNSUPPORTED;
  }
}


/**
  Translate the INTx# reported by the PCI device into the true IRQ vector
  number from the internal IRQ routing information.

  @param[in]      This          Protocol instance pointer.
  @param[in]      PciBus        Base Bus
  @param[in]      PciDevice     Base Device
  @param[in]      PciFunction   Base Function
  @param[in, out] Pirq          Input is INTx# pin reported by device(0-based). Output is
                                PIRQ number that this INTx# will be routed to.
  @param[out]     PciIrq        The IRQ number will be assigned to this device.

  @retval         EFI_SUCCESS    Irq translated
**/
EFI_STATUS
EFIAPI
TranslatePirq (
  IN EFI_LEGACY_BIOS_PLATFORM_PROTOCOL            *This,
  IN UINTN                                        PciBus,
  IN UINTN                                        PciDevice,
  IN UINTN                                        PciFunction,
  IN OUT UINT8                                    *Pirq,
  OUT UINT8                                       *PciIrq
  )

{
  EFI_STATUS                    Status;
  VOID                          *PirqTable;

  DEBUG ((DEBUG_INFO, "TranslatePirq\n"));

  Status = mPrivateData.IrqTableInfo->Initialize (mPrivateData.IrqTableInfo, mPrivateData.PciAutoDetect);
  ASSERT_EFI_ERROR (Status);

  Status = mPrivateData.IrqTableInfo->GetPirqTable (mPrivateData.IrqTableInfo, &PirqTable);
  ASSERT_EFI_ERROR (Status);

  return PlatformTranslatePirq (
           PirqTable,
           IrqPriorityTable,
           MAX_IRQ_PRIORITY_ENTRIES,
           PciBus,
           PciDevice,
           PciFunction,
           Pirq,
           PciIrq
           );
}


/**
  Attempt to legacy boot the BootOption. If the EFI contexted has been
  compromised this function will not return.

  @param[in] This                     Protocol instance pointer.
  @param[in] BbsDevicePath            EFI Device Path from BootXXXX variable.
  @param[in] BbsTable                 Internal BBS table.
  @param[in] LoadOptionsSize          Size of LoadOption in size.
  @param[in] LoadOptions              LoadOption from BootXXXX variable
  @param[in] EfiToLegacy16BootTable   Pointer to BootTable structure

  @retval    EFI_SUCCESS              Removable media not present
**/
EFI_STATUS
EFIAPI
PrepareToBoot (
  IN  EFI_LEGACY_BIOS_PLATFORM_PROTOCOL           *This,
  IN  BBS_BBS_DEVICE_PATH                         *BbsDevicePath,
  IN  VOID                                        *BbsTable,
  IN  UINT32                                      LoadOptionsSize,
  IN  VOID                                        *LoadOptions,
  IN  VOID                                        *EfiToLegacy16BootTable
  )
{
  UINT16                      DevicePathType;
  UINT16                      Index;
  UINT16                      Priority;
  BBS_TABLE                   *LocalBbsTable;
  DEVICE_PRODUCER_DATA_HEADER *SioPtr;

  Index     = 0x00;
  Priority  = 0x00;
  //
  // Set how Gate A20 is gated by hardware
  //
  SioPtr                  = &(((EFI_TO_COMPATIBILITY16_BOOT_TABLE *)EfiToLegacy16BootTable)->SioData);
  SioPtr->Flags.A20Kybd   = 1;
  SioPtr->Flags.A20Port90 = 1;

  //
  // bugbug This is eventually specified by SETUP variables
  //
  LocalBbsTable = BbsTable;

  //
  // There are 2 cases that must be covered.
  // Case 1: Booting to a legacy OS - BbsDevicePath is non-NULL.
  // Case 2: Booting to an EFI aware OS - BbsDevicePath is NULL.
  //         We need to perform the PrepareToBoot function to assign
  //         drive numbers to HDD devices to allow the shell or EFI
  //         to access them.
  //
  if (BbsDevicePath != NULL) {
    DevicePathType = BbsDevicePath->DeviceType;
  } else {
    DevicePathType = BBS_HARDDISK;
  }
  //
  // Skip the boot devices where priority is set by BDS and set the next one
  //
  for (Index = 0; Index < MAX_BBS_ENTRIES; Index++) {
    if ((LocalBbsTable[Index].BootPriority != BBS_UNPRIORITIZED_ENTRY) &&
        (LocalBbsTable[Index].BootPriority != BBS_IGNORE_ENTRY) &&
        (LocalBbsTable[Index].BootPriority != BBS_LOWEST_PRIORITY) &&
        (Priority <= LocalBbsTable[Index].BootPriority)
        ) {
      Priority = (UINT16) (LocalBbsTable[Index].BootPriority + 1);
    }
  }

  switch (DevicePathType) {
  case BBS_FLOPPY:
  case BBS_HARDDISK:
  case BBS_CDROM:
  case BBS_EMBED_NETWORK:
    for (Index = 0; Index < MAX_BBS_ENTRIES; Index++) {
      if ((LocalBbsTable[Index].BootPriority == BBS_UNPRIORITIZED_ENTRY) &&
          (LocalBbsTable[Index].DeviceType == DevicePathType)
          ) {
        LocalBbsTable[Index].BootPriority = Priority;
        ++Priority;
      }
    }
    break;

  case BBS_BEV_DEVICE:
    //
    // search bbs table for HDDs & pick choice.
    // bugbug for now only do HDD
    //
    for (Index = 0; Index < MAX_BBS_ENTRIES; Index++) {
      if ((LocalBbsTable[Index].BootPriority == BBS_UNPRIORITIZED_ENTRY) &&
          (LocalBbsTable[Index].Class == 01) &&
          (LocalBbsTable[Index].SubClass == 01)
          ) {
        LocalBbsTable[Index].BootPriority = Priority;
        ++Priority;
      }
    }
    break;

  case BBS_USB:
  case BBS_PCMCIA:
  case BBS_UNKNOWN:
  default:
    break;
  }
  //
  // Set priority for rest of devices
  //
  for (Index = 0; Index < MAX_BBS_ENTRIES; Index++) {
    if (LocalBbsTable[Index].BootPriority == BBS_UNPRIORITIZED_ENTRY) {
      LocalBbsTable[Index].BootPriority = Priority;
      ++Priority;
    }
  }

  PrepareToBootEbdaBcv ();

  return EFI_SUCCESS;
}


/**
  Restore legacy ahci rom setting before booting to legacy os.

  @param[in] Event
  @param[in] Context
**/
VOID
EFIAPI
AhciLegacyBootEvent (
  IN EFI_EVENT           Event,
  IN VOID                *Context
  )
{
  //
  // it's because native ahci driver can not provide int13 service.
  // so we should restore to legacy ahci oprom setting.
  //
  if (mLegacyAhciRom) {
    LegacyBootAhciOpromRestore ();
  }
}


/**
  This function is used to update ACPI table root pointer to CSM E/F segment.
  TXT ACM will check ACPI table from legacy region, EFI OS will fail to update
  that if it does an EFI boot. So we update it here.

  @param[in] Event       A pointer to the Event that triggered the callback.
  @param[in] Context     A pointer to private data registered with the callback function.

  @returns   EFI_SUCCESS Always.
**/
VOID
EFIAPI
PrepareAcpiCallback (
  IN EFI_EVENT        Event,
  IN VOID             *Context
  )
{
  PrepareAcpiInLegacyRegion ();
}


/**
  Install Driver to produce Legacy BIOS protocol.

  @param[in] ImageHandle   Handle of image
  @param[in] SystemTable   Pointer to System Table

  @retval    EFI_SUCCESS   Legacy Bios Platform protocol installed
  @retval    Other         No protocol installed, unload driver.
**/
EFI_STATUS
EFIAPI
LegacyBiosPlatformInstall (
  IN EFI_HANDLE           ImageHandle,
  IN EFI_SYSTEM_TABLE     *SystemTable
  )
{
  EFI_STATUS                       Status;
  EFI_EVENT                        ReadyToBootEvent;
  EFI_EVENT                        LegacyBootEvent;

  Status = gBS->LocateProtocol (
                  &gEfiPciRootBridgeIoProtocolGuid,
                  NULL,
                  (VOID **) &mPrivateData.PciRootBridgeIo
                  );
  ASSERT_EFI_ERROR (Status);

  Status = gBS->LocateProtocol (
                  &gIrqTableInfoProtocolGuid,
                  NULL,
                  (VOID **) &mPrivateData.IrqTableInfo
                  );
  ASSERT_EFI_ERROR (Status);
  mPrivateData.PciAutoDetect = TRUE;

  mPrivateData.Signature                            = LEGACY_BIOS_PLATFORM_INSTANCE_SIGNATURE;
  mPrivateData.LegacyBiosPlatform.GetPlatformInfo   = GetPlatformInfo;
  mPrivateData.LegacyBiosPlatform.GetPlatformHandle = GetPlatformHandle;
  mPrivateData.LegacyBiosPlatform.SmmInit           = SmmInit;
  mPrivateData.LegacyBiosPlatform.PlatformHooks     = PlatformHooks;
  mPrivateData.LegacyBiosPlatform.GetRoutingTable   = GetRoutingTable;
  mPrivateData.LegacyBiosPlatform.TranslatePirq     = TranslatePirq;
  mPrivateData.LegacyBiosPlatform.PrepareToBoot     = PrepareToBoot;
  mPrivateData.ImageHandle                          = ImageHandle;
  mPrivateData.Handle                               = NULL; // Make a new handle and install the protocol

  Status = gBS->InstallProtocolInterface (
                  &mPrivateData.Handle,
                  &gEfiLegacyBiosPlatformProtocolGuid,
                  EFI_NATIVE_INTERFACE,
                  &mPrivateData.LegacyBiosPlatform
                  );

  //
  // Copy ACPI Root Pointer to CSM E/F segment, this is required by ACM.
  //
  Status = EfiCreateEventReadyToBootEx (
             TPL_CALLBACK,
             PrepareAcpiCallback,
             NULL,
             &ReadyToBootEvent
             );
  ASSERT_EFI_ERROR (Status);

  //
  // Register a callback to restore legacy ahci rom env.
  // it's used to work with native ahci driver.
  //
  Status = EfiCreateEventLegacyBootEx (
                  TPL_CALLBACK,
                  AhciLegacyBootEvent,
                  (VOID *)&ImageHandle,
                  &LegacyBootEvent
                  );

  return Status;
}
